#include "RenderViewThread.h"
#include <QGLWidget>
#include <QTime>
#include <QDebug>
#include <cmath>
#include <ctime>
#include <QGLShader>
#include <QGLShaderProgram>
#include <QImage>

QMap<QString, GLuint> cTextureMap;

RenderViewThread::RenderViewThread(QGLWidget *view,
                                   unsigned int framerateInHz,
                                   bool is2D,
                                   const QColor& clearColor,
                                   QObject *parent) :
    QThread(parent),
    mCamera(QVector3D(10,10,10),
            QVector3D(0,0,0)),
    mView(view),
    mClearColor(clearColor),
    mFrameRateInHz(framerateInHz),
    mWidth(0),
    mHeight(0),
    mIs2D(is2D),
    mDoResize(false),
    mDoRender(false),
    mFrameSleepTime(1000.0f/mFrameRateInHz),
    mLightPos(10, 0, -10),
    mAngle(0),
    mAngleAccel(0),
    mVertShader(NULL),
    mFragShader(NULL),
    mShader(NULL),
    mCube(QVector3D(), 5, 0, 0, 0),
    mSphere(QVector3D(0,0,0), 5, 1),
    mSun(QVector3D(0,0,0), 0.5, .5)//,
    //mS(QVector3D(5, 15, 5), 5, 1)
{
  mView->makeCurrent();
  glewInit();
  mVertShader = new QGLShader(QGLShader::Vertex, mView);
  mFragShader = new QGLShader(QGLShader::Fragment, mView);
  mShader = new QGLShaderProgram(mView);
  if (mVertShader->compileSourceFile("Shaders/normal.vert"))
  {
    if (mFragShader->compileSourceFile("Shaders/normal.frag"))
    {
      mShader->addShader(mVertShader);
      mShader->addShader(mFragShader);
      mShader->bindAttributeLocation("tangent", 6);
      mShader->bindAttributeLocation("bitangent", 7);
      mShader->link();
    }
  }
  mSphere.setShader(mShader);
  mSphere.setColor(QColor(255,255,255));
  //mS.setShader(mShader);
  //mS.setColor(QColor(255,255,255));
  mSun.setShader(NULL);
  mSun.setColor(QColor(255,255,0));
  mView->doneCurrent();
}

RenderViewThread::~RenderViewThread() {}

Camera& RenderViewThread::camera()
{
  return mCamera;
}

void RenderViewThread::resize(const QSize &size)
{
  mWidth = size.width();
  mHeight = size.height();
  mDoResize = true;
}

void RenderViewThread::setClearColor(const QColor &clearColor)
{
    mClearColor = clearColor;
}

void RenderViewThread::initializeGL()
{

  mView->makeCurrent();
  glAlphaFunc( GL_GREATER, 0.1 );
  glBlendFunc( GL_SRC_ALPHA, GL_ONE );
  glClearDepth(1.0f);
  glEnable(GL_DEPTH_TEST);
  glDepthFunc(GL_LEQUAL);
  glEnable(GL_TEXTURE_2D);
  mView->doneCurrent();
  resize(mView->size());
}

void RenderViewThread::beginGLFrame()
{
  glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
  glClearColor(mClearColor.redF(),
               mClearColor.greenF(),
               mClearColor.blueF(),
               mClearColor.alphaF());
  glLoadIdentity();
  mCamera.look();
}

void RenderViewThread::endGLFrame()
{
  glColor3f(1,1,1);
  mView->swapBuffers();
}

void RenderViewThread::begin2D(unsigned int w, unsigned int h)
{
  glDisable( GL_DEPTH_TEST );
  glMatrixMode( GL_MODELVIEW );
  glPushMatrix();
  glLoadIdentity();
  glMatrixMode( GL_PROJECTION );
  glPushMatrix();
  glLoadIdentity();
  gluOrtho2D( 0, w, 0, h );
}

void RenderViewThread::end2D()
{
  glPopMatrix();
  glMatrixMode( GL_MODELVIEW );
  glPopMatrix();
  glEnable( GL_DEPTH_TEST );
}

void RenderViewThread::loadGLTexture(const QString &filename, GLuint& tex)
{
  if (!cTextureMap.contains(filename))
  {
    //load image
    QImage temp(filename);
    if (temp.isNull())
    {
      temp = QImage(16, 16, QImage::Format_RGB32);
      temp.fill(Qt::green);
      qDebug() << "Can't find image";
    }
    QImage glformatted(QGLWidget::convertToGLFormat(temp)); //convert to gl format
    glGenTextures(1, &cTextureMap[filename]); //create 1 texture
    glBindTexture(GL_TEXTURE_2D, cTextureMap[filename]); //create 2d texture at ret
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); //filter when image is smaller than texture: good filter
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); //filter when image is larger than texture: good filter
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
    glTexImage2D(GL_TEXTURE_2D, //create 2d texture
                 0, //level of detail
                 3, //number of data components (rgb = 3)
                 glformatted.width(), glformatted.height(), //size of texture
                 0, //border
                 GL_RGBA, //use rgb in this order
                 GL_UNSIGNED_BYTE, //image data is unsigned bytes
                 glformatted.bits()); //pointer to image data
  }
  tex = cTextureMap[filename];
}


void RenderViewThread::update()
{
  mAngle = (mAngle+mAngleAccel)%360;
  //float a((float)mAngleAccel*3.14159/180.0);
  float a((float)1*3.14159/180.0);
  float x(mLightPos.x()), y(mLightPos.y()), z(mLightPos.z());
  mLightPos.setZ(z*cos(a)-x*sin(a));
  mLightPos.setX(z*sin(a)+x*cos(a));
  mLightPos.setY(y);
}

void RenderViewThread::render()
{
  static bool isBound;
  int length = 5;
  static int i(0);
  isBound = false;
  GLuint img, nml;
  loadGLTexture("images/brickwork-diffuse.jpg", img);
  loadGLTexture("images/brickwork-normal.jpg", nml);


  if (mShader != NULL && mShader->isLinked())
  {
    isBound = mShader->bind();
    if (isBound)
    {
      mShader->setUniformValue("sinetime", (float)(sin((i++)*(3.14159/180.0))));
      mShader->setUniformValue("lightDir", mLightPos);
      mShader->setUniformValue("diffuseTex", 0);
      mShader->setUniformValue("normalTex", 1);
    }
  }

  glRotatef(mAngle, 0,1,0);
  glColor3f(1,1,1);
  glPolygonMode( GL_FRONT_AND_BACK, GL_FILL );
  glActiveTexture(GL_TEXTURE0);
  glBindTexture(GL_TEXTURE_2D, img);
  glActiveTexture(GL_TEXTURE1);
  glBindTexture(GL_TEXTURE_2D, nml);
  mSphere.render();
  //mS.render();
  glActiveTexture(GL_TEXTURE0);
  glBindTexture(GL_TEXTURE_2D, 0);
  glActiveTexture(GL_TEXTURE1);
  glBindTexture(GL_TEXTURE_2D, 0);
  if (mShader != NULL && mShader->isLinked() && isBound)
  {
    mShader->release();
  }

  glTranslatef(mLightPos.x(), mLightPos.y(), mLightPos.z());
  mSun.render();
}

void RenderViewThread::stop()
{
  mDoRender = false;
}

void RenderViewThread::run()
{
  mDoRender = true;
  initializeGL();
  unsigned int frames = 0;
  QTime t(QTime::currentTime());
  t.start();
  while (mDoRender)
  {
    update();
    mView->makeCurrent();
    if (mDoResize)
    {
      glViewport(0,0,mWidth, mHeight);
      glMatrixMode(GL_PROJECTION);
      glLoadIdentity();
      //glOrtho(-10, 10, -10, 10, -100, 100);
      gluPerspective(45.0f,(GLfloat)mWidth/(GLfloat)mHeight,0.1f,100.0f);
      glMatrixMode(GL_MODELVIEW);
      mDoResize = false;
    }
    beginGLFrame();
    if (mIs2D)
    {
      begin2D(mWidth, mHeight);
      render();
      end2D();
    }
    else
    {
      render();
    }
    endGLFrame();
    ++frames;
    msleep(mFrameSleepTime);
    if (t.elapsed() >= 1000)
    {
      emit fps(frames);
      t.restart();
      frames = 0;
    }
  }
}
